/**
 * 
 */
package com.primestone.cim.meterreadings;

import static com.primestone.cim.util.StringUtil._NULL_BOOLEAN_;
import static com.primestone.cim.util.StringUtil._NULL_STRING_;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.xml.ws.Holder;

import ch.iec.tc57._2011.meterreadings.EndDeviceEvent;
import ch.iec.tc57._2011.meterreadings.MeterReading;
import ch.iec.tc57._2011.meterreadings.MeterReading.CustomerAgreement;
import ch.iec.tc57._2011.meterreadings.MeterReading.IntervalBlocks;
import ch.iec.tc57._2011.meterreadings.MeterReading.IntervalBlocks.IntervalReadings;
import ch.iec.tc57._2011.meterreadings.MeterReading.IntervalBlocks.IntervalReadings.ReadingQualities;
import ch.iec.tc57._2011.meterreadings.MeterReading.IntervalBlocks.Pending;
import ch.iec.tc57._2011.meterreadings.MeterReading.MeterAsset;
import ch.iec.tc57._2011.meterreadings.MeterReading.Readings;
import ch.iec.tc57._2011.meterreadings.MeterReading.ServiceDeliveryPoint;
import ch.iec.tc57._2011.meterreadings.MeterReadings;
import ch.iec.tc57._2011.meterreadings.ReadingType;
import ch.iec.tc57._2011.meterreadings.Status;
import ch.iec.tc57._2011.schema.message.HeaderType;
import ch.iec.tc57._2011.schema.message.MessageProperty;

import com.primestone.cim.meterreadings.beans.ReadingsQueryResult;
import com.primestone.cim.util.DateUtil;
import com.primestone.cim.util.NumberUtil;
import com.primestone.cim.util.StringUtil;

/**
 * @author cesar.mateus
 *
 */
public class Completer {


	/**
	 * 
	 * @param allMap
	 * @param intervalsMap
	 * @param readingsMap
	 */
	private static void classifyIntervalReadingsAndReadings( Map<String, List<ReadingsQueryResult>> allMap,
			Map<String, List<ReadingsQueryResult>> intervalsMap, Map<String, ReadingsQueryResult> readingsMap) {
		Set<String> keySet = allMap.keySet();
		List<ReadingsQueryResult> readings;
		for (String readingTypeCode : keySet) {
			readings = allMap.get(readingTypeCode);
			// Si hay mas de una lectura con el mismo reading type code, se trata de un intervalo.
			// Sino es una lectura suelta.
			if (readings.size() > 1) {
				intervalsMap.put(readingTypeCode, readings);
			} else {
				readingsMap.put(readingTypeCode, readings.get(0));
			}
		}
	}

	/**
	 * 
	 * @param queryResult
	 * @param queryResultLPAndRegisters
	 * @param queryResultEvents
	 */
	private static void classifyLPRegistersAndEvents( List<ReadingsQueryResult> queryResult,
			List<ReadingsQueryResult> queryResultLPAndRegisters, List<ReadingsQueryResult> queryResultEvents) {
		for (ReadingsQueryResult readingQueryResult : queryResult) {
			if (readingQueryResult.getVarType().equals(StringUtil.VARTYPE_EVE)) {
				queryResultEvents.add(readingQueryResult);
			} else {
				queryResultLPAndRegisters.add(readingQueryResult);
			}
		}
	}

	/**
	 * 
	 * @param idCustomer
	 * @return
	 */
	public static CustomerAgreement fillCustomerAgreement(String idCustomer) {
		CustomerAgreement customerAgreement = new CustomerAgreement();
		customerAgreement.setMRID(idCustomer); // required
		return customerAgreement;
	}

	/**
	 * 
	 * @param result
	 * @return
	 */
	public static EndDeviceEvent fillEndDeviceEvent(ReadingsQueryResult result) {
		EndDeviceEvent endDeviceEvent = new EndDeviceEvent();
		endDeviceEvent.setMRID(_NULL_STRING_);
		endDeviceEvent.setCategory(result.getDescVar()); // required (aca va el codigo cim)
		GregorianCalendar gCal = new GregorianCalendar();
		gCal.setTime(result.getDatetime());
		endDeviceEvent.setCreatedDateTime(DateUtil.getXMLGregorianCalendarFromGregorianCalendar(gCal)); // required
		endDeviceEvent.setReason(result.getDescVar());
		endDeviceEvent.setSeverity(_NULL_STRING_);//TODO porque coloque el usagedata aca?? result.getUsageData()
		if (StringUtil.safe(result.getIdSocket())) {
			endDeviceEvent.setUserID(new StringBuilder(result.getIdSocket()).append(StringUtil.UNDERSCORE).append(result.getIdDevice()).toString());	
		} else {
			endDeviceEvent.setUserID(result.getIdDevice());
		}
		
		return endDeviceEvent;
	}

	/**
	 * 
	 * @param endDeviceEventList
	 * @param queryResultEvents
	 */
	public static void fillEndDeviceEventsList(List<EndDeviceEvent> endDeviceEventList, List<ReadingsQueryResult> queryResultEvents) {
		// Iterar sobre una lista de eventos, segun el query result.
		for (ReadingsQueryResult readingQueryResult : queryResultEvents) {
			if (StringUtil.safe(readingQueryResult.getDescVar())) {
				endDeviceEventList.add(fillEndDeviceEvent(readingQueryResult));	
			}
		}
	}

	/**
	 * 
	 * @param entry
	 * @return
	 */
	public static IntervalBlocks fillIntervalBlocks(Entry<String, List<ReadingsQueryResult>> entry) {
		IntervalBlocks intervalBlocks = new IntervalBlocks();
		fillIntervalReadingsList(intervalBlocks.getIntervalReadings(), entry.getValue());	

		//intervalBlocks.setPending(fillPending());
		intervalBlocks.setReadingType(fillIntervalBlocksReadingType(entry.getKey())); // required

		return intervalBlocks;
	}

	/**
	 * 
	 * @param intervalBlocksList
	 * @param intervalsMap
	 */
	public static void fillIntervalBlocksList(List<IntervalBlocks> intervalBlocksList, Map<String, List<ReadingsQueryResult>> intervalsMap) {
		// Iterar sobre una lista de intervalos lp, segun el query result. 
		Set<Entry<String, List<ReadingsQueryResult>>> entrySet = intervalsMap.entrySet();
		for (Entry<String, List<ReadingsQueryResult>> entry : entrySet) {
			intervalBlocksList.add(fillIntervalBlocks(entry));
		}
	}

	/**
	 * 
	 * @param readingTypeCode
	 * @return
	 */
	public static MeterReading.IntervalBlocks.ReadingType fillIntervalBlocksReadingType(String readingTypeCode) {
		MeterReading.IntervalBlocks.ReadingType readingType = new MeterReading.IntervalBlocks.ReadingType();
		readingType.setRef(readingTypeCode);
		return readingType;
	}

	/**
	 * 
	 * @param readingQueryResult
	 * @return
	 */
	public static IntervalReadings fillIntervalReadings(ReadingsQueryResult readingQueryResult) {
		IntervalReadings intervalReadings = new IntervalReadings();

		setIntervalReadingTimeStampAndValue(intervalReadings, readingQueryResult);

		//fillIntervalReadingsReadingQualitiesList(intervalReadings.getReadingQualities());

		return intervalReadings;
	}

	/**
	 * 
	 * @param intervalReadingsList
	 * @param readingsQueryResult
	 */
	public static void fillIntervalReadingsList(List<IntervalReadings> intervalReadingsList, List<ReadingsQueryResult> readingsQueryResult) {
		// Iterar sobre una lista de intervalos lp, segun el request. 
		for (ReadingsQueryResult readingQueryResult : readingsQueryResult) {
			intervalReadingsList.add(fillIntervalReadings(readingQueryResult));	
		}
	}

	/**
	 * 
	 * @return
	 */
	public static ReadingQualities fillIntervalReadingsReadingQualities(/*aca falta ver que param se necesita*/) {
		ReadingQualities readingQualities = new ReadingQualities();
		readingQualities.setQuality(_NULL_STRING_); // required
		return readingQualities;
	}

	/**
	 * 
	 * @param readingQualitiesList
	 */
	public static void fillIntervalReadingsReadingQualitiesList(List<ReadingQualities> readingQualitiesList) {
		// TODO Iterar sobre una lista de reading qualities, segun el request. 
		readingQualitiesList.add(fillIntervalReadingsReadingQualities());
	}

	/**
	 * 
	 * @param idDevice
	 * @return
	 */
	public static MeterAsset fillMeterAsset(String idDevice) {
		MeterAsset meterAsset = new MeterAsset();
		meterAsset.setMRID(idDevice); // required

		// TODO Aun no se dispone de el dato para completar el reason del status.
		Status status = new Status();
		status.setDateTime(DateUtil.getXMLGregorianCalendarFromGregorianCalendar());
		//status.setReason(_NULL_STRING_);
		status.setValue(StringUtil.STATUS_OK); // required
		meterAsset.setStatus(status);
		return meterAsset;
	}

	/**
	 * 
	 * @param intervalsMap
	 * @param readingsMap
	 * @param idCustomer
	 * @param idDevice
	 * @param idSocket
	 * @param queryResultEvents
	 * @return
	 */
	public static MeterReading fillMeterReading(
			Map<String, List<ReadingsQueryResult>> intervalsMap, Map<String, ReadingsQueryResult> readingsMap, 
			String idCustomer, String idDevice, String idSocket, List<ReadingsQueryResult> queryResultEvents) {
		MeterReading meterReading = new MeterReading();
		meterReading.setCustomerAgreement(fillCustomerAgreement(idCustomer));
		meterReading.setMeterAsset(fillMeterAsset(idDevice)); // required
		meterReading.setServiceDeliveryPoint(fillServiceDeliveryPoint(idSocket));

		fillEndDeviceEventsList(meterReading.getEndDeviceEvents(), queryResultEvents);
		fillIntervalBlocksList(meterReading.getIntervalBlocks(), intervalsMap);
		fillReadingsList(meterReading.getReadings(), readingsMap);

		return meterReading;
	}

	/**
	 * 
	 * @param meterReadingList
	 * @param idCustomer
	 * @param idSocket
	 * @param idDevice
	 * @param intervalsMap
	 * @param readingsMap
	 * @param queryResultEvents
	 */
	public static void fillMeterReadingList(List<MeterReading> meterReadingList, String idCustomer, String idSocket, String idDevice, 
			Map<String, List<ReadingsQueryResult>> intervalsMap, Map<String, ReadingsQueryResult> readingsMap, List<ReadingsQueryResult> queryResultEvents) {

		meterReadingList.add(fillMeterReading(intervalsMap, readingsMap, idCustomer, idDevice, idSocket, queryResultEvents));
	}

	/**
	 * 
	 * @param queryResult
	 * @return
	 */
	public static MeterReadings fillMeterReadings(List<ReadingsQueryResult> queryResult) {
		MeterReadings meterReadings = new MeterReadings();

		String[] idCustomerIdSocketIdDevice = findIdCustomerAndIdSocketAndIdDevice(queryResult);

		List<ReadingsQueryResult> queryResultLPAndRegisters = new ArrayList<ReadingsQueryResult>();
		List<ReadingsQueryResult> queryResultEvents = new ArrayList<ReadingsQueryResult>();
		classifyLPRegistersAndEvents(queryResult, queryResultLPAndRegisters, queryResultEvents);

		Map<String, List<ReadingsQueryResult>> allLPAndRegistersMap = new HashMap<String, List<ReadingsQueryResult>>();
		fillReadingTypeList(meterReadings.getReadingType(), queryResultLPAndRegisters, allLPAndRegistersMap);

		Map<String, List<ReadingsQueryResult>> intervalsMap = new HashMap<String, List<ReadingsQueryResult>>(); 
		Map<String, ReadingsQueryResult> readingsMap = new HashMap<String, ReadingsQueryResult>();
		classifyIntervalReadingsAndReadings(allLPAndRegistersMap, intervalsMap, readingsMap);

		fillMeterReadingList(meterReadings.getMeterReading(), 
				idCustomerIdSocketIdDevice[0], idCustomerIdSocketIdDevice[1], idCustomerIdSocketIdDevice[2], 
				intervalsMap, readingsMap, queryResultEvents);

		return meterReadings;
	}

	/**
	 * 
	 * @return
	 */
	public static Pending fillPending(/*aca falta ver que param se necesita*/) {
		Pending pending = new Pending();
		pending.setMultiplyBeforeAdd(_NULL_BOOLEAN_);
		pending.setOffset(NumberUtil._NULL_BIGINTEGER_);
		pending.setScalarDenominator(NumberUtil._NULL_BIGINTEGER_);
		pending.setScalarFloat(NumberUtil._NULL_FLOAT_);
		pending.setScalarNumerator(NumberUtil._NULL_BIGINTEGER_);
		pending.setReadingType(fillPendingReadingType()); // required

		return pending;
	}

	/**
	 * 
	 * @return
	 */
	public static MeterReading.IntervalBlocks.Pending.ReadingType fillPendingReadingType(/*aca falta ver que param se necesita*/) {
		MeterReading.IntervalBlocks.Pending.ReadingType readingType = new MeterReading.IntervalBlocks.Pending.ReadingType();
		readingType.setRef(_NULL_STRING_);
		return readingType;
	}

	/**
	 * 
	 * @param entry
	 * @return
	 */
	public static Readings fillReadings(Entry<String, ReadingsQueryResult> entry) {
		ReadingsQueryResult result = entry.getValue();
		Readings readings = new Readings();

		setReadingTimeStampAndValue(readings, result);

		//fillReadingsReadingQualitiesList(readings.getReadingQualities());

		readings.setReadingType(fillReadingsReadingType(entry.getKey())); // required

		return readings;
	}

	/**
	 * 
	 * @param readingsList
	 * @param readingsMap
	 */
	public static void fillReadingsList(List<Readings> readingsList, Map<String, ReadingsQueryResult> readingsMap) {
		// Iterar sobre una lista de intervalos lp, segun el query result.
		Set<Entry<String,ReadingsQueryResult>> entrySet = readingsMap.entrySet();
		for (Entry<String, ReadingsQueryResult> entry : entrySet) {
			readingsList.add( fillReadings(entry) );
		}
	}

	/**
	 * 
	 * @return
	 */
	public static MeterReading.Readings.ReadingQualities fillReadingsReadingQualities(/*aca falta ver que param se necesita*/) {
		MeterReading.Readings.ReadingQualities readingQualities = new MeterReading.Readings.ReadingQualities();
		readingQualities.setQuality(_NULL_STRING_); // required
		return readingQualities;
	}

	/**
	 * 
	 * @param readingQualitiesList
	 */
	public static void fillReadingsReadingQualitiesList(List<MeterReading.Readings.ReadingQualities> readingQualitiesList) {
		// TODO Iterar sobre una lista de reading qualities, segun el request. 
		readingQualitiesList.add(fillReadingsReadingQualities());
	}

	/**
	 * 
	 * @param readingTypeCode
	 * @return
	 */
	public static MeterReading.Readings.ReadingType fillReadingsReadingType(String readingTypeCode) {
		MeterReading.Readings.ReadingType readingType = new MeterReading.Readings.ReadingType();
		readingType.setRef(readingTypeCode);
		return readingType;
	}

	/**
	 * 
	 * @param result
	 * @param map
	 * @return
	 */
	public static ReadingType fillReadingType(ReadingsQueryResult result, Map<String, List<ReadingsQueryResult>> map) {
		ReadingType readingType = new ReadingType();
//		List<String> infoMRID = ReadingTypeComposer.composeReadingTypeMRID(result);
//		readingType.setMRID(infoMRID.get(0)); // required
//		readingType.setAliasName(infoMRID.get(1));
		
		readingType.setMRID(result.getCode()); // required
		readingType.setAliasName(result.getAliasName());

		readingType.setChannelNumber( 
				result.getVarType().equals(StringUtil.VARTYPE_LP) ? 
						BigInteger.valueOf(Long.valueOf(result.getChannel())) :
							NumberUtil._NULL_BIGINTEGER_
				);
//		readingType.setDefaultQuality(_NULL_STRING_);
//		readingType.setDefaultValueDataType(_NULL_STRING_);
//		readingType.setDynamicConfiguration(_NULL_STRING_);

		readingType.setIntervalLength(
				result.getVarType().equals(StringUtil.VARTYPE_LP) ?
						Float.valueOf(result.getInterval()) :
							NumberUtil._NULL_FLOAT_
				);

//		readingType.setKind(_NULL_STRING_);
//		readingType.setMultiplier(_NULL_STRING_);
		readingType.setName(result.getDescVar()); // required
//		readingType.setReverseChronology(_NULL_BOOLEAN_);
		
//		readingType.setUnit(  // required
//				result.getVarType().equals(StringUtil.VARTYPE_LP) || result.getVarType().equals(StringUtil.VARTYPE_REG) ? 
//						result.getUom() : 
//							_NULL_STRING_
//				);
		
		populateMap(map, readingType.getMRID(), result);
		return readingType;
	}

	/**
	 * 
	 * @param readingTypeList
	 * @param queryResult
	 * @param map
	 */
	public static void fillReadingTypeList(List<ReadingType> readingTypeList, List<ReadingsQueryResult> queryResult, Map<String, List<ReadingsQueryResult>> map) {
		// Iterar sobre una lista de lecturas, segun el queryResult.
		List<String> mrids = new ArrayList<String>();
		for (ReadingsQueryResult result : queryResult) {
			ReadingType fillReadingType = fillReadingType(result, map);
			if (fillReadingType != null) {
				if (!mrids.contains(fillReadingType.getMRID())) {
					mrids.add(fillReadingType.getMRID());
					readingTypeList.add(fillReadingType);
				}
			}
		}
	}

	/**
	 * 
	 * @param idSocket
	 * @return
	 */
	public static ServiceDeliveryPoint fillServiceDeliveryPoint(String idSocket) {
		ServiceDeliveryPoint serviceDeliveryPoint = new ServiceDeliveryPoint();

		serviceDeliveryPoint.setMRID(idSocket); // required

		return serviceDeliveryPoint;
	}

	/**
	 * 
	 * @param queryResult
	 * @return
	 */
	private static String[] findIdCustomerAndIdSocketAndIdDevice(List<ReadingsQueryResult> queryResult) {
		String idCustomer = _NULL_STRING_;
		String idDevice = _NULL_STRING_;
		String idSocket = _NULL_STRING_;
		for (ReadingsQueryResult result : queryResult) {
			idDevice = result.getIdDevice();
			idSocket = result.getIdSocket();
			idCustomer = result.getIdClient();
			// FIXME El siguiente break implica que cada response siempre trae solo lecturas del mismo customer, socket, medidor. Lo cual puede cambiar. Cuidado.
			break;
		}
		return new String[]{idCustomer, idSocket, idDevice};
	}

	/**
	 * 
	 * @param map
	 * @param readingTypeCode
	 * @param readingQueryResult
	 */
	private static void populateMap(Map<String, List<ReadingsQueryResult>> map, String readingTypeCode, ReadingsQueryResult readingQueryResult) {
		List<ReadingsQueryResult> readingsQueryResult;
		Set<String> keySet = map.keySet();
		if (keySet.contains(readingTypeCode)) {
			readingsQueryResult = map.get(readingTypeCode);
		} else {
			readingsQueryResult = new ArrayList<ReadingsQueryResult>();
		}
		readingsQueryResult.add(readingQueryResult);
		map.put(readingTypeCode, readingsQueryResult);
	}

	/**
	 * 
	 * @param header
	 * @param ticket
	 */
	public static void putTicket(Holder<HeaderType> header, String ticket) {
		if (header.value != null) {			
			MessageProperty messageProperty = new MessageProperty();
			messageProperty.setName(StringUtil.TICKET_FIELD_NAME);
			messageProperty.setValue(ticket);
			header.value.getProperty().add(messageProperty);			
		}
	}

	/**
	 * 
	 * @param intervalReadings
	 * @param result
	 */
	public static void setIntervalReadingTimeStampAndValue(IntervalReadings intervalReadings, ReadingsQueryResult result) {
		//Hacer parse de result.getDatetime() y usarlo como el valor de timestamp
		GregorianCalendar gCal = new GregorianCalendar();
		gCal.setTime(result.getDatetime());
		intervalReadings.setTimeStamp(DateUtil.getXMLGregorianCalendarFromGregorianCalendar(gCal)); // required
		intervalReadings.setValue(Float.valueOf(result.getUsageData()));
	}

	/**
	 * 
	 * @param readings
	 * @param result
	 */
	public static void setReadingTimeStampAndValue(Readings readings, ReadingsQueryResult result) {
		//Hacer parse de result.getDatetime() y usarlo como el valor de timestamp
		GregorianCalendar gCal = new GregorianCalendar();
		gCal.setTime(result.getDatetime());
		readings.setTimeStamp(DateUtil.getXMLGregorianCalendarFromGregorianCalendar(gCal)); // required
		readings.setValue(Float.valueOf(result.getUsageData()));
	}

}
